﻿using RWSBeeldbank.Common;
using RWSBeeldbank.Data;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.System.UserProfile;
using Windows.UI.Notifications;
using Windows.UI.Popups;
using Windows.Data.Json;
using Callisto.OAuth;
using Windows.Foundation;
using Windows.UI.Xaml.Controls;
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.Data.Xml.Dom;
using System.Xml;
using System.Threading;
using RWSBeeldbank.DataModel;

namespace RWSBeeldbank.Utility
{

    public class Tools
    {
        public static SemaphoreSlim m_lockFile = new SemaphoreSlim(initialCount: 1);
        public static List<int> justRatedId = new List<int>();

        /// <summary>
        /// Fills a DataRequest object that can be shared to other applications.  
        /// </summary>
        /// <param name="item">MediaObject item</param>
        /// <param name="request">DataRequest object with initial values</param>
        /// <returns>Filled DataRequest object</returns>
        public static DataRequest SetMessageRequest(MediaObjectDataItem item, DataRequest request)
        {
            var resourceLoader = new ResourceLoader();

            request.Data.Properties.Title = resourceLoader.GetString("MessagePropertiesTitle") + ": " + item.Title;
            request.Data.Properties.Description = resourceLoader.GetString("MessagePropertiesDescription");

            // Share mediaObject text
            string textMessage = resourceLoader.GetString("MessageHeader");
            textMessage += "<br />" + resourceLoader.GetString("MessageTitle") + ": " + item.Title;
            textMessage += "<br />" + resourceLoader.GetString("MessageShortDescription") + ": " + item.AltTitle;
            textMessage += "<br /><br />" + resourceLoader.GetString("MessageMediaInfo");
            textMessage += "<br /><br />" + resourceLoader.GetString("MessageDescription") + ": " + item.Description;
            if ((!String.IsNullOrEmpty(item.DateCreated)) && (item.DateCreated != "01-01-1900"))
                textMessage += "<br />" + resourceLoader.GetString("MessageDateCreated") + ": " + item.DateCreated;
            if (!String.IsNullOrEmpty(item.Author))
                textMessage += "<br />" + resourceLoader.GetString("MessageAuthor") + ": " + item.Author;
            if (!String.IsNullOrEmpty(item.Location))
                textMessage += "<br />" + resourceLoader.GetString("MessageLocation") + ": " + item.Location;
            if (!String.IsNullOrEmpty(item.Province))
                textMessage += "<br />" + resourceLoader.GetString("MessageProvince") + ": " + item.Province;
            if (!String.IsNullOrEmpty(item.GPS))
                textMessage += "<br />" + resourceLoader.GetString("MessageGPS") + ": " + item.GPS;
            if (!String.IsNullOrEmpty(item.Keywords))
                textMessage += "<br />" + resourceLoader.GetString("MessageKeywords") + ": " + item.Keywords;
            if (!String.IsNullOrEmpty(item.Themes))
                textMessage += "<br />" + resourceLoader.GetString("MessageThemes") + ": " + item.Themes;
            if (!String.IsNullOrEmpty(item.InventoryId))
                textMessage += "<br />" + resourceLoader.GetString("MessageInventoryId") + ": " + item.InventoryId;
            textMessage += "<br />" + resourceLoader.GetString("MessageDirectLink") + ": <a href='" + item.DirectLink + "'>" + item.DirectLink + "</a>";
            textMessage += "<br /><img src=\"https://beeldbank.rws.nl/MediaService/[1366-" + item.FileNumber + "]\" />";
            textMessage += "<br />" + resourceLoader.GetString("MessageCopyRight");
            if (!String.IsNullOrEmpty(item.Author))
                textMessage += " / " + item.Author;
            request.Data.SetHtmlFormat(HtmlFormatHelper.CreateHtmlFormat(textMessage)); // when sending HTML images can't be added

            // This is not working for the mail Application, but we add it in for other apps
            var reference = RandomAccessStreamReference.CreateFromUri(new Uri(item.LargeImagePath.AbsoluteUri));
            request.Data.Properties.Thumbnail = reference;
            request.Data.SetBitmap(reference);

            return request;
        }

        /// <summary>
        /// Copies an image from the internet (http protocol) locally to the AppData LocalFolder.
        /// This is used by some methods (like the SecondaryTile constructor) that do not support 
        /// referencing images over http but can reference them using the ms-appdata protocol.  
        /// </summary>
        /// <param name="internetUri">The path (URI) to the image on the internet</param>
        /// <param name="uniqueName">A unique name for the local file</param>
        /// <returns>Path to the image that has been copied locally</returns>
        public static async Task<Uri> GetUriLocalImageAsync(string internetUri, string uniqueName)
        {
            if (string.IsNullOrEmpty(internetUri))
                return null;

            using (var response = await HttpWebRequest.CreateHttp(internetUri).GetResponseAsync())
            {
                using (var stream = response.GetResponseStream())
                {
                    var desiredName = string.Format("{0}", uniqueName);
                    var file = await ApplicationData.Current.LocalFolder.CreateFileAsync(desiredName, CreationCollisionOption.ReplaceExisting);

                    using (var filestream = await file.OpenStreamForWriteAsync())
                    {
                        await stream.CopyToAsync(filestream);
                        return new Uri(string.Format("ms-appdata:///local/{0}", desiredName), UriKind.Absolute);
                    }
                }
            }
        }

        public static async Task<ulong> StoreImageInPicturesLibraryAsync(MediaObjectDataItem item, int size)
        {
            Uri sourceURI = null;
            string messageTxt = "";
            switch (size)
            {
                case 1 :
                    sourceURI = new Uri(item.TileImagePath.AbsoluteUri);
                    messageTxt = "InfoSmallImageStored";
                    break;
                case 2:
                    sourceURI = new Uri(item.LargeImagePath.AbsoluteUri);
                    messageTxt = "InfoLargeImageStored";
                    break;
                case 3:
                    string originalUrl = item.LargeImagePath.ToString().Replace(Constants.LARGE_WIDTH_TARGET, Constants.TILE_WIDTH_SOURCE + "[");
                    messageTxt = "InfoOriginalImageStored";
                    sourceURI = new Uri(originalUrl);
                    break;
            }
            // Retrieve the image and store it in the local PicturesLibrary
            var fileName = item.Title + Constants.IMAGE_EXTENSION;

            HttpClient http = new HttpClient();
            Stream resp = await http.GetStreamAsync(sourceURI);

            var storageFile = await KnownFolders.PicturesLibrary.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);

            ulong result = 0;
            using (var storageStream = await storageFile.OpenAsync(FileAccessMode.ReadWrite))
            {
                await RandomAccessStream.CopyAndCloseAsync(resp.AsInputStream(), storageStream.GetOutputStreamAt(0));
            }

            SendNotification(messageTxt);            
            
            return result;
        }

        public static async Task<ulong> StoreVideoInVideosLibraryAsync(MediaObjectDataItem item)
        {
            Uri sourceURI = null;
            sourceURI = new Uri(item.VideoFile);

            // Retrieve the video and store it in the local VideosLibrary
            var fileName = item.Id.ToString() + Constants.VIDEO_EXTENSION;
            
            HttpClient http = new HttpClient();
            Stream resp = await http.GetStreamAsync(sourceURI);

            var storageFile = await KnownFolders.VideosLibrary.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);

            ulong result = 0;
            using (var storageStream = await storageFile.OpenAsync(FileAccessMode.ReadWrite))
            {
                await RandomAccessStream.CopyAndCloseAsync(resp.AsInputStream(), storageStream.GetOutputStreamAt(0));
            }

            SendNotification("InfoOriginalVideoStored");

            return result;
        }

        public static async Task PutImageOnLockScreen(MediaObjectDataItem item)
        {
            // Set the correct URL
            string originalUrl = item.LargeImagePath.ToString().Replace(Constants.LARGE_WIDTH_TARGET, Constants.TILE_WIDTH_SOURCE + "[");
            Uri sourceURI = new Uri(originalUrl);

            // Create the stream
            HttpClient http = new HttpClient();
            Stream resp = await http.GetStreamAsync(sourceURI);

            // Retrieve the image and store it in the local PicturesLibrary
            var fileName = item.Title + Constants.IMAGE_EXTENSION;

            StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;

            var storageFile = await localFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);

            using (var storageStream = await storageFile.OpenAsync(FileAccessMode.ReadWrite))
            {
                await RandomAccessStream.CopyAndCloseAsync(resp.AsInputStream(), storageStream.GetOutputStreamAt(0));
            }

            // Create storageFile
            if (storageFile != null)
            {
                try
                {
                    // Application now has access to the downloaded file, setting image to lockscreen.
                    // This will fail if the file is an invalid format.
                    await LockScreen.SetImageFileAsync(storageFile);
                    SendNotification("InfoLockScreenChanged");
                }
                catch (Exception)
                {
                    SendNotification("ErrorLockScreenNotChanged");
                }
            }
            else
            {
                SendNotification("ErrorLockScreenNotChanged");
            }
        }

        private static async void SendNotification(string messageTxt)
        {
            var resourceLoader = new ResourceLoader();

            var notifier = ToastNotificationManager.CreateToastNotifier();

            // Make sure notifications are enabled
            if (notifier.Setting != NotificationSetting.Enabled)
            {
                var dialog = new MessageDialog(resourceLoader.GetString("ErrorNoNotifications"));
                await dialog.ShowAsync();
                return;
            }

            // Get a toast template and insert a text node containing a message
            var template = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText01);
            var element = template.GetElementsByTagName("text")[0];
            element.AppendChild(template.CreateTextNode(resourceLoader.GetString(messageTxt)));

            // Schedule the toast to appear 2 seconds from now
            var date = DateTimeOffset.Now.AddSeconds(2);
            var stn = new ScheduledToastNotification(template, date);
            notifier.AddToSchedule(stn);
        }
        
        public static void DetermineGPS(MediaObjectDataItem mediaObject)
        {
            var resourceLoader = new ResourceLoader();

            if ((mediaObject.Latitude > 0.0) && (mediaObject.Longitude > 0.0))
            {
                // Conversion from decimal to degrees
                string LatDeg = Convert.ToInt32(mediaObject.Latitude).ToString() + " ";
                string LatMin = Convert.ToInt32((mediaObject.Latitude % 1) * 60).ToString() + "' ";
                string LatSec = Convert.ToInt32((((mediaObject.Latitude % 1) * 60) % 1) * 60).ToString() + "\" N, ";
                string LongDeg = Convert.ToInt32(mediaObject.Longitude).ToString() + " ";
                string LongMin = Convert.ToInt32((mediaObject.Longitude % 1) * 60).ToString() + "' ";
                string LongSec = Convert.ToInt32((((mediaObject.Longitude % 1) * 60) % 1) * 60).ToString() + "\" E";

                string Hoogte = "";
                if (mediaObject.Height > 0)
                    Hoogte = ", " + resourceLoader.GetString("Height") + ": " + mediaObject.Height.ToString() + " " + resourceLoader.GetString("Meter");
                mediaObject.GPS = LatDeg + LatMin + LatSec + LongDeg + LongMin + LongSec + Hoogte;
            }
        }

        public static void CreateUniqueIdCombination(MediaObjectDataItem mediaObject)
        {
            mediaObject.UniqueId = mediaObject.Id.ToString() + "|" + mediaObject.Group.UniqueId;
        }

        public static string GetGroupId(string uniqueId)
        {
            return uniqueId.Split('|')[uniqueId.Split('|').Length - 1];
        }


// ab
        public static async void DeleteFilesOlderThenAsync(int expireInDays)
        {
            try
            {
                var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
                var localCacheFolder = await localFolder.GetFolderAsync("cache\\");
                var files = await localCacheFolder.GetFilesAsync();

                foreach (var file in files)
                {
                    var basicFileProperties = await file.GetBasicPropertiesAsync();

                    // Do NOT use file.DateCreated as this is never updated after the file has been created
                    if ((System.DateTime.Now - basicFileProperties.DateModified).TotalDays > expireInDays)
                    {
                        await file.DeleteAsync();
                    }
                }

            }
            catch
            {
                // error with file deletion
            }
        }



        private static async Task<bool> DoesFileExistAsync(string fileName)
        {
	        try {
                var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
                await localFolder.GetFileAsync(fileName);
		        return true;
	        } catch {
		        return false;
	        }
        }

        private static async Task<bool> IsFileExpiredAsync(string fileName, int expireInSeconds)
        {
            var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
            var file = await localFolder.GetFileAsync(fileName);

            var basicFileProperties = await file.GetBasicPropertiesAsync();

            // Do NOT use file.DateCreated as this is never updated after the file has been created
            if ((System.DateTime.Now - basicFileProperties.DateModified).TotalSeconds > expireInSeconds)
            {
                // cache expired
                return true;
            }
            else
            {
                // cache NOT expired
                return false;
            }
        }


        public static async Task<JsonArray> cachedFeed(Uri feedUri)
        {
            String fileProperties = "";
            bool specificSearchQuery = false;

            if (!feedUri.Query.Equals(""))
            {
                WwwFormUrlDecoder decoder = new WwwFormUrlDecoder(feedUri.Query);
                // Add the properties of the URL feed to the filename
                foreach (var item in decoder)
                {
                    if (item.Name.Equals("searchString"))
                    {
                        // Never cache feeds with a custom search string
                        if (!item.Value.Equals(""))
                        {
                            specificSearchQuery = true;
                        }
                    }
                    else
                    {
                        fileProperties += "_" + item.Name + "_" + item.Value;
                    }
                }
            }

            // get beginning of cache filename out of URI
            string regex = ".*/([^\\?]*)";
            System.Text.RegularExpressions.RegexOptions options = ((System.Text.RegularExpressions.RegexOptions.IgnorePatternWhitespace | System.Text.RegularExpressions.RegexOptions.Singleline) | System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            System.Text.RegularExpressions.Regex reg = new System.Text.RegularExpressions.Regex(regex, options);
            string filenameBegin = reg.Match(feedUri.AbsoluteUri).Groups[1].Value;


            //
            // Retrieve mediaObject data from Beeldbank webserver
            //
            var client = new HttpClient();
            client.MaxResponseContentBufferSize = Constants.MAX_RESPONSE_BUFFERSIZE;

            //
            // check if requested content is already in local storage
            //
            var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
            var jsonFeed = new JsonArray();
            String filename = "cache\\" + filenameBegin + fileProperties + ".txt";

            //
            // if there is a search string or the cache file does not extist or is expired
            //
            if (specificSearchQuery || (await Utility.Tools.DoesFileExistAsync(filename) == false || await Utility.Tools.IsFileExpiredAsync(filename, Constants.FEED_CACHE_TIME_SECONDS) == true))
            {
                //
                // get the content form the URL
                //
                var response = await client.GetAsync(feedUri);
                var result = await response.Content.ReadAsStringAsync();
                // Parse the JSON mediaObject data
                jsonFeed = JsonArray.Parse(result);

                // Write json result to the file
                StorageFile topMediaListFile = await localFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
                await FileIO.WriteTextAsync(topMediaListFile, result);
            }
            else
            {
                //
                // get the content form the cache file
                //
                StorageFile topMediaListFile = await localFolder.GetFileAsync(filename);
                var result = await FileIO.ReadTextAsync(topMediaListFile);
                jsonFeed = JsonArray.Parse(result);
            }

            return jsonFeed;
        }

        /// <summary>
        /// add rated item to file
        /// </summary>
        /// <param name="mediaObjectId"></param>
        /// <param name="ratingValue"></param>
        public static async void markItemAsRated(int mediaObjectId, int ratingValue)
        {
            var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
            String filename = "rating\\rating.xml";
            String resultXML = "";


            XmlDocument xmlDoc = new XmlDocument();

            // Read rating file
            if(await DoesFileExistAsync(filename))
            {
                StorageFile ratingFile = await localFolder.GetFileAsync(filename);
                if(ratingFile != null)
                {
                    var result = await FileIO.ReadTextAsync(ratingFile);
                    resultXML = result.ToString();

                    if (!String.IsNullOrEmpty(resultXML))
                    {
                        xmlDoc.LoadXml(resultXML);
                    }
                }
            }

            if (xmlDoc.FirstChild == null)
            {
                // Create the root element
                var rootNode = xmlDoc.CreateElement("RatingList");
                xmlDoc.AppendChild(rootNode);
            }

            // Create a new <Category> element and add it to the root node
            XmlElement parentNode = xmlDoc.CreateElement("Rating");
            // Set attribute name and value!
            parentNode.SetAttribute("mediaObjectId", mediaObjectId.ToString());
            parentNode.SetAttribute("ratingValue", ratingValue.ToString());
            //xmlDoc.DocumentElement.PrependChild(parentNode);
            xmlDoc.DocumentElement.AppendChild(parentNode);

            //xmlDoc.GetXml()
            //"<RatingList><Rating mediaObjectId=\"4324324\" ratingValue="\3\"\></RatingList>"


            // Write vote to file
            StorageFile topMediaListFile = await localFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
            
            // Read rating file
            if (await DoesFileExistAsync(filename))
            {

                await m_lockFile.WaitAsync();
                try
                {
                    await FileIO.WriteTextAsync(topMediaListFile, xmlDoc.GetXml());
                }
                catch
                {
                    throw new System.InvalidOperationException("file already in use - m_lockFile should have avoided this");
                }
                finally
                {
                    m_lockFile.Release();
                }
            }
        }


        /// <summary>
        /// make shure items that have been rated can't be rated again
        /// </summary>
        /// <param name="mediaObjectId"></param>
        /// <returns>bool</returns>
        public static async Task<bool> isItemRated(int mediaObjectId)
        {
            // if the item is rated, then return true and the API call will not be made
            // if the item isn't rated, it could be that it is in the process of being rated, therefore not automatically false
            if (await getItemRating(mediaObjectId) != -1)
            {
                return true;
            }
            else
            {
                // Er was een grote issue met het toevoegen van ratings is de database als er twee keer direct achter elkaar 
                // een zelfde mediaobject ID ingeschoten werdt. Vanwege Async methodes, bleek de check of het item al bestond 
                // niet te werken waardoor het ID dan ook 2x toegevoegd werdt. De oorzaak hiervan is dat 2 taken tegelijk de
                // zelfde check uitvoeren en de eerste taak het bestand nog niet heeft geupdate voordat de tweede taak checked
                // of het bestand met het item bestaat.
                //
                // Opgelost door een globale lijst bij te houden, die checked of het item net gerate is (justRatedId)
                if (justRatedId.Contains(mediaObjectId))
                {
                    return true;
                }
                else
                {
                    justRatedId.Add(mediaObjectId);
                    return false;
                }
            }
        }
        /// <summary>
        /// make shure items that have been rated can't be rated again
        /// </summary>
        /// <param name="mediaObjectId"></param>
        /// <returns>bool</returns>
        public static async Task<int> getItemRating(int mediaObjectId)
        {
            var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
            String filename = "rating\\rating.xml";
            String resultXML = "";
            XmlDocument xmlDoc = new XmlDocument();

            // Read rating file
            if(await DoesFileExistAsync(filename))
            {
                StorageFile ratingFile = await localFolder.GetFileAsync(filename);
                if(ratingFile != null)
                {
                    var result = await FileIO.ReadTextAsync(ratingFile);
                    resultXML = result.ToString();

                    if (!String.IsNullOrEmpty(resultXML))
                    {
                        xmlDoc.LoadXml(resultXML);

                        if (xmlDoc.FirstChild != null)
                        {
                            XmlNodeList elemList = xmlDoc.GetElementsByTagName("Rating");
                            for (int i = 0; i < elemList.Count; i++)
                            {
                                if (mediaObjectId == Convert.ToInt32(elemList[i].Attributes[0].NodeValue))
                                {
                                    return Convert.ToInt32(elemList[i].Attributes[1].NodeValue);
                                }
                            }
                        }
                    }
                }
            }
            return -1;
        }


    }

 
    public static class FrameworkElementExtensions
    {
        public static FrameworkElement FindDescendantByName(this FrameworkElement element, string name)
        {
            if (element == null || string.IsNullOrWhiteSpace(name)) { return null; }

            if (name.Equals(element.Name, StringComparison.OrdinalIgnoreCase))
            {
                return element;
            }
            var childCount = VisualTreeHelper.GetChildrenCount(element);
            for (int i = 0; i < childCount; i++)
            {
                var result = (VisualTreeHelper.GetChild(element, i) as FrameworkElement).FindDescendantByName(name);
                if (result != null) { return result; }
            }
            return null;
        }
    }


}
